{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2d849d21",
   "metadata": {},
   "source": [
    "Update to week2 (openai) lab3 to include:\n",
    "- Use of Claude (\"claude-3-5-sonnet-20240620\") as one of the models\n",
    "- An email class for consistent structure\n",
    "- Inclusion of standard footer and log of all emails sent\n",
    "- I also used google to send the mails rather than sendgrid with my .env file as a lazy way to include to and from emails and my app password.\n",
    "\n",
    "I ran this a few times to test it and I found it interesting that Claude came back a couple of times complaining about being the Head of Compliance, e.g. \"As an AI assistant, I don't actually hold the position of Head of Compliance at ComplAI. I'm here to help you craft messages, not to impersonate real people or positions\".  I thought this was interesting!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "acb8975a",
   "metadata": {},
   "outputs": [],
   "source": [
    "!py -m pip install email-validator\n",
    "!py -m pip install html2text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a1fa836",
   "metadata": {},
   "outputs": [],
   "source": [
    "#imports\n",
    "from dotenv import load_dotenv\n",
    "from openai import AsyncOpenAI\n",
    "from agents import Agent, Runner, trace, function_tool, OpenAIChatCompletionsModel, input_guardrail, GuardrailFunctionOutput\n",
    "from typing import Dict\n",
    "import os\n",
    "from pydantic import BaseModel, EmailStr, Field\n",
    "import asyncio\n",
    "import smtplib\n",
    "from email.mime.text import MIMEText\n",
    "from datetime import date, datetime\n",
    "import csv\n",
    "import html2text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca23f793",
   "metadata": {},
   "outputs": [],
   "source": [
    "#.env\n",
    "load_dotenv(override=True)\n",
    "\n",
    "openai_api_key = os.getenv('OPENAI_API_KEY')\n",
    "google_api_key = os.getenv('GOOGLE_API_KEY')\n",
    "groq_api_key = os.getenv('GROQ_API_KEY')\n",
    "anthropic_api_key = os.getenv('ANTHROPIC_API_KEY')\n",
    "\n",
    "if openai_api_key:\n",
    "    print(f\"OpenAI API Key exists and begins {openai_api_key[:8]}\")\n",
    "else:\n",
    "    print(\"OpenAI API Key not set\")\n",
    "\n",
    "if anthropic_api_key:\n",
    "    print(f\"Anthropic API Key exists and begins {openai_api_key[:13]}\")\n",
    "else:\n",
    "    print(\"Anthropic API Key not set\")\n",
    "\n",
    "if google_api_key:\n",
    "    print(f\"Google API Key exists and begins {google_api_key[:2]}\")\n",
    "else:\n",
    "    print(\"Google API Key not set (and this is optional)\")\n",
    "\n",
    "if groq_api_key:\n",
    "    print(f\"Groq API Key exists and begins {groq_api_key[:4]}\")\n",
    "else:\n",
    "    print(\"Groq API Key not set (and this is optional)\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26449a3e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# sales agent instructions / personas\n",
    "instructions1 = \"You are a sales agent working for ComplAI, \\\n",
    "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n",
    "You write professional, serious cold emails.\"\n",
    "\n",
    "instructions2 = \"You are a humorous, engaging sales agent working for ComplAI, \\\n",
    "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n",
    "You write witty, engaging cold emails that are likely to get a response.\"\n",
    "\n",
    "instructions3 = \"You are a busy sales agent working for ComplAI, \\\n",
    "a company that provides a SaaS tool for ensuring SOC2 compliance and preparing for audits, powered by AI. \\\n",
    "You write concise, to the point cold emails.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "276ba79e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# non openai models\n",
    "GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta/openai/\"\n",
    "GROQ_BASE_URL = \"https://api.groq.com/openai/v1\"\n",
    "ANTHROPIC_BASE_URL = \"https://api.anthropic.com/v1\"\n",
    "\n",
    "gemini_client = AsyncOpenAI(base_url=GEMINI_BASE_URL, api_key=google_api_key)\n",
    "groq_client = AsyncOpenAI(base_url=GROQ_BASE_URL, api_key=groq_api_key)\n",
    "anthropic_client = AsyncOpenAI(base_url=ANTHROPIC_BASE_URL, api_key=anthropic_api_key)\n",
    "\n",
    "gemini_model = OpenAIChatCompletionsModel(model=\"gemini-2.0-flash\", openai_client=gemini_client)\n",
    "llama3_3_model = OpenAIChatCompletionsModel(model=\"llama-3.3-70b-versatile\", openai_client=groq_client)\n",
    "qwen3_model = OpenAIChatCompletionsModel(model=\"qwen/qwen3-32b\", openai_client=groq_client)\n",
    "claude_model = OpenAIChatCompletionsModel(model=\"claude-3-5-sonnet-20240620\", openai_client=anthropic_client)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e8f799e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# map sales agents to models\n",
    "sales_agent1 = Agent(name=\"ClaudeSales Agent\",instructions=instructions1,model=claude_model)\n",
    "sales_agent2 = Agent(name=\"Gemini Sales Agent\", instructions=instructions2, model=gemini_model)\n",
    "sales_agent3 = Agent(name=\"Llama3_3 Sales Agent\",instructions=instructions3,model=llama3_3_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fc0923f3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# convert sales agents to tools\n",
    "description = \"Write a cold sales email\"\n",
    "\n",
    "tool1 = sales_agent1.as_tool(tool_name=\"sales_agent1\", tool_description=description)\n",
    "tool2 = sales_agent2.as_tool(tool_name=\"sales_agent2\", tool_description=description)\n",
    "tool3 = sales_agent3.as_tool(tool_name=\"sales_agent3\", tool_description=description)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f00b69dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# class for email structure\n",
    "\n",
    "class EmailStructure(BaseModel):\n",
    "    to: EmailStr\n",
    "    from_: EmailStr = Field(alias=\"from\")\n",
    "    subject: str\n",
    "    body: str\n",
    "    body_html: str | None = None\n",
    "    footer: str | None = None\n",
    "    tags: dict[str, str] = Field(default_factory=dict)\n",
    "\n",
    "    def generate_footer(self):\n",
    "        \"\"\"Generate a copyright footer with the current year.\"\"\"\n",
    "        year = date.today().year\n",
    "        self.footer = f\"<br><br>--<br>© {year} All rights reserved.\"\n",
    "\n",
    "    def body_text(self):\n",
    "        return html2text.html2text(self.body_html).strip()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c3d93b6d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Send email tool which includes a footer and logs the emails sent\n",
    "\n",
    "@function_tool\n",
    "def send_html_email(subject: str, html_body: str) -> Dict[str, str]:\n",
    "    \"\"\"Send an HTML email using structured input from the EmailStructure class.\"\"\"\n",
    "\n",
    "    gmail_user = os.getenv(\"FROM_EMAIL\") \n",
    "    gmail_app_password = os.getenv(\"GOOGLE_APP_PW\")\n",
    "    to = os.getenv(\"TO_EMAIL\") \n",
    "\n",
    "    #create instance of emailstructure\n",
    "    email_structure = EmailStructure(\n",
    "        to=to,\n",
    "        **{\"from\": gmail_user},\n",
    "        subject=subject,\n",
    "        body=html_body, \n",
    "        body_html=html_body\n",
    "    )\n",
    "\n",
    "    # Generate footer\n",
    "    email_structure.generate_footer()\n",
    "\n",
    "    # Combine HTML body with footer\n",
    "    final_html_body = email_structure.body_html + (email_structure.footer or \"\")\n",
    "\n",
    "    msg = MIMEText(final_html_body, \"html\")\n",
    "    msg['Subject'] = email_structure.subject\n",
    "    msg['From'] = email_structure.from_\n",
    "    msg['To'] = email_structure.to\n",
    "\n",
    "    LOG_FILE = \"email_log.csv\"\n",
    "\n",
    "    def log_email(email_obj):\n",
    "        file_exists = os.path.isfile(LOG_FILE)\n",
    "        with open(LOG_FILE, mode=\"a\", newline=\"\", encoding=\"utf-8\") as csvfile:\n",
    "            writer = csv.writer(csvfile)\n",
    "            if not file_exists:\n",
    "                writer.writerow([\"to_email\", \"from_email\", \"subject\", \"body\", \"date\", \"email_creator\"])\n",
    "            writer.writerow([\n",
    "                email_structure.to,\n",
    "                email_structure.from_,\n",
    "                email_structure.subject,\n",
    "                email_structure.body_text(),\n",
    "                email_structure.tags\n",
    "            ])\n",
    "\n",
    "    try:\n",
    "        with smtplib.SMTP('smtp.gmail.com', 587) as server:\n",
    "            server.starttls()\n",
    "            server.login(gmail_user, gmail_app_password)\n",
    "            server.send_message(msg)\n",
    "        print(\"Email sent successfully!\")\n",
    "        log_email(email_structure)\n",
    "\n",
    "    except Exception as e:\n",
    "        print(f\"Failed to send email: {e}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab54166e",
   "metadata": {},
   "outputs": [],
   "source": [
    "#tools for email inputs\n",
    "subject_instructions = \"You can write a subject for a cold sales email. \\\n",
    "You are given a message and you need to write a subject for an email that is likely to get a response.\"\n",
    "\n",
    "html_instructions = \"You can convert a text email body to an HTML email body. \\\n",
    "You are given a text email body which might have some markdown \\\n",
    "and you need to convert it to an HTML email body with simple, clear, compelling layout and design.  Do not include a footer as this will be added by another tool.\"\n",
    "\n",
    "subject_writer = Agent(name=\"Email subject writer\", instructions=subject_instructions, model=\"gpt-4o-mini\")\n",
    "subject_tool = subject_writer.as_tool(tool_name=\"subject_writer\", tool_description=\"Write a subject for a cold sales email\")\n",
    "\n",
    "html_converter = Agent(name=\"HTML email body converter\", instructions=html_instructions, model=\"gpt-4o-mini\")\n",
    "html_tool = html_converter.as_tool(tool_name=\"html_converter\",tool_description=\"Convert a text email body to an HTML email body\")\n",
    "\n",
    "email_tools = [subject_tool, html_tool, send_html_email]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ec2702f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "#emailer agent (will receive the handoff)\n",
    "instructions =\"You are an email formatter and sender. You receive the body of an email to be sent. \\\n",
    "You first use the subject_writer tool to write a subject for the email, then use the html_converter tool to convert the body to HTML. \\\n",
    "You must add a tag to the email structure to indicate the agent that created the email. \\\n",
    "Finally, you use the send_html_email tool to provide the structure, include a footer andsend the email with the subject and HTML body.\"\n",
    "\n",
    "\n",
    "emailer_agent = Agent(\n",
    "    name=\"Email Manager\",\n",
    "    instructions=instructions,\n",
    "    tools=email_tools,\n",
    "    model=\"gpt-4o-mini\",\n",
    "    handoff_description=\"Convert an email to HTML and send it\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cab14aaf",
   "metadata": {},
   "outputs": [],
   "source": [
    "#tool lists\n",
    "tools = [tool1, tool2, tool3]\n",
    "handoffs = [emailer_agent]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4863df34",
   "metadata": {},
   "outputs": [],
   "source": [
    "#input guardrail\n",
    "class NameCheckOutput(BaseModel):\n",
    "    is_name_in_message: bool\n",
    "    name: str\n",
    "\n",
    "guardrail_agent = Agent( \n",
    "    name=\"Name check\",\n",
    "    instructions=\"Check if the user is including someone's personal name in what they want you to do.\",\n",
    "    output_type=NameCheckOutput,\n",
    "    model=\"gpt-4o-mini\"\n",
    ")\n",
    "\n",
    "@input_guardrail\n",
    "async def guardrail_against_name(ctx, agent, message):\n",
    "    result = await Runner.run(guardrail_agent, message, context=ctx.context)\n",
    "    is_name_in_message = result.final_output.is_name_in_message\n",
    "    return GuardrailFunctionOutput(output_info={\"found_name\": result.final_output},tripwire_triggered=is_name_in_message)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "870556bd",
   "metadata": {},
   "outputs": [],
   "source": [
    "#sales manager agent initialisation\n",
    "\n",
    "sales_manager_instructions = \"You are a sales manager working for ComplAI. You use the tools given to you to generate cold sales emails. \\\n",
    "You never generate sales emails yourself; you always use the tools. \\\n",
    "You try all 3 sales agent tools at least once before choosing the best one. \\\n",
    "You can use the tools multiple times if you're not satisfied with the results from the first try but no more than 3 times. \\\n",
    "You select the single best email using your own judgement of which email will be most effective. \\\n",
    "After picking the email, you handoff to the Email Manager agent to format and send the email.\"\n",
    "\n",
    "careful_sales_manager = Agent(\n",
    "    name=\"Sales Manager\",\n",
    "    instructions=sales_manager_instructions,\n",
    "    tools=tools,\n",
    "    handoffs=[emailer_agent],\n",
    "    model=\"gpt-4o-mini\",\n",
    "    input_guardrails=[guardrail_against_name]\n",
    "    )\n",
    "\n",
    "#message = \"Send out a cold sales email addressed to Dear CEO from Alice\"\n",
    "message = \"Send out a cold sales email addressed to Dear CEO from the head of compliance\"\n",
    "\n",
    "with trace(\"Protected Automated SDR\"):\n",
    "    result = await Runner.run(careful_sales_manager, message)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
